package sia.tpIntro;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


public class GeneticAlgorithm {

	static SimulationParams params; 
	
	/**
	 * @param args
	 * @throws Exception 
	 * @throws Exception 
	 */
	public static void main( String[] args ) throws Exception {
		
		params = new SimulationParams().configWith(args);
	
		List<Genotype> population = null;
		List<Genotype> parents = null;
		List<Genotype> offspring = null;
		Integer currGeneration = 0;
		Double bestGen = 0D;
		
		Genotype.setPrecision( params.precision );
		
		population = initializePopulation( params.initialPop );
	
		System.out.println( population );
			
		while( currGeneration < params.generationCount ) {
			parents = selection( population );
			offspring = reproduction( parents );
			
			//Combino la poblacion con los hijos y reemplazo
			population.addAll( offspring );
			population = replacement( population );
			
			currGeneration++;
		}

		System.out.println( population );	
		
		//Busco el mas apto de la poblacion:
		for( Genotype gen : population )
			if( bestGen < Problem.maximize(gen) )
				bestGen = gen.getFenotype();
		
		System.out.println("Best of the population: " + bestGen );
		//f(MAX) 0.74
		//f(MIN) es equivalente a (1/f)(MAX) = 16.667
	}

	private static List<Genotype> initializePopulation( int size ) throws Exception {
		List<Genotype> population = new ArrayList<Genotype>();
		double randomValue;
		
		for( int i=0 ; i<size; i++ ) {
			randomValue = Math.random();
			population.add( new Genotype( randomValue ) );
		}
			
		return population;
	}

	private static List<Genotype> selection( List<Genotype> population ) {
		//Aca se puden ir probando distintas selecciones:
		//
		//return minusSelection(population);
		//return universalSelection(population);
		//return randomSelection(population);
		return rouletteSelection( population, params.selectionCount );
	}
	
	private static List<Genotype> replacement(List<Genotype> population) {
		return rouletteSelection(population, params.initialPop);
	}
	
	private static List<Genotype> rouletteSelection( List<Genotype> population, int size ) {
		List<Double> nRandoms = new ArrayList<Double>();
		List<Genotype> selected = new ArrayList<Genotype>();
		
		Double aptitudeTotal = 0.0;
		Double currAptitude;
		
		//Genero n randoms
		for( int i=0 ; i<size ; i++ ) {
			nRandoms.add( Math.random() );
		}
		
		//Ordeno los randoms
		Collections.sort( nRandoms );

		//Pondero cada genotype y le asigno su aptitud
		for( Genotype genotype : population ) {
			currAptitude = Problem.maximize( genotype );
			genotype.setAptitude( currAptitude );
			aptitudeTotal += currAptitude;
		}
		
		//Itero sobre los genotypes y genero las aptitudes Acumuladas
		currAptitude = 0.0;
		for( Genotype genotype : population ) {
			currAptitude += genotype.aptitude/aptitudeTotal;
			genotype.setAcumAptitude( currAptitude );
		}
		
		//Itero sobre los n randoms
		for( Double currRandom : nRandoms ) {
			//Por cada random me fijo que genotype cae
			for ( Genotype genotype : population ) {
				if( genotype.acumAptitude >= currRandom ) {
					selected.add( genotype );
					break;
				}
			}
		}

		return selected;
	}

	private static List<Genotype> reproduction( List<Genotype> parents ) {
		List<Genotype> sons = new ArrayList<Genotype>();
		int crossPoint = (int) Math.floor( Math.random() * ( params.precision - 1 ) );
		
		//Primero hacemos el cruce
		for( int i=0 ; i<parents.size() ; i++ ) {
			for( int j=i+1 ; j<parents.size() ; j++ ) {
				sons.add( (Genotype) parents.get(i).cross( parents.get(j), crossPoint ) );
			}
		}
		
		//Despues la mutacion
		for( int i=0 ; i<sons.size() ; i++ ) {
				sons.get(i).mutate(params.mutationP);				
		}

		return sons;
	}
}